#! /usr/bin/python

import getopt, os.path, re, sys

#
# Options used to control function behaviour, set in main and
# used in various places
binarynames = False # Whether to print binary names while traverse


def message(type, mess):
    """ message:
          Print messages, using colors if requested.

        Parameters:
            type = Any of the following:

                    INFO    -> Bold white text
                    ERROR   -> Red text
                    SUCCESS -> Cyan colored text
                    normal  -> default looking text

            mess = The message to print (will be converted using 'str()')
    """

    # Colors used for printing
    color            = {}
    color['INFO']    = '\033[0;1m'   # Bold white
    color['ERROR']   = '\033[31m'    # Red
    color['SUCCESS'] = '\033[36m'    # Cyan
    color['normal']  = '\033[0m'     # Restore default

    try:
        print color[type] + type + color['normal'] + ': ' + str(mess)
    except KeyError:
        print type + ': ' + str(mess)


def walkcallback(arg, dirname, names):
    """ walkcallback:
          This callback function is used to process any found Java files
          during a file tree walk (preferable done with os.path.walk).

          It checks a 'package foo.bar' against the directory that the 
          found Java file is located in, and issues a warning if there
          seems to be a problem (i.e. the package statement differs in
          where the file should be located -> problems later on).

        Parameters:
          arg = Argument propagted from orignating call to os.path.walk
          dirname = The name of the directory that is currentl under consideration
                    (it is given relative to the starting directory)
          names = The files present in the directory 'dirname'
    """

    global binarynames

    for x in names:
        if x.endswith('.java'):
            try:
                fd = file(dirname + '/' + x, 'r')

                for line in fd:
                    res = re.search('[ \t]*package[ \t]+([.\w]+[ \t]*);', line)

                    if res:
                        pkg = res.group(1)

                        if binarynames:
                            message('INFO', 'Checking ' + pkg + '.' + x.replace('.java', ''))

                        if not pkg.replace('.', '/') == dirname:
                            message('ERROR', 'Bad package in file: ' + dirname + '/' + x + ' (Found package: ' + pkg +')')
                        break

                fd.close()
            except IOError, (errno, strerror):
                message('ERROR', dirname + '/' + x)
                message('ERROR', strerror)
                sys.exit(1)
    


def help():
    """ help:
          Print usage information.
    """

    message('INFO', '')
    message('INFO', 'Usage: ' + sys.argv[0] + ' <options>')
    message('INFO', '')
    message('INFO', 'Where <options> is any of:')
    message('INFO', '')
    message('INFO', '   -h : Print this help and exit, cancels any other parameters')
    message('INFO', '        number of times')
    message('INFO', '   -l : List fully qualified names for the class being checked (i.e.')
    message('INFO', '        the binary name of the class; see this link: ')
    message('INFO', '        http://java.sun.com/docs/books/jls/second_edition/html/binaryComp.doc.html#44909')
    message('INFO', '        for a discussion of binary names.)')
    message('INFO', '   -p : Add a package root to check, may be specified several times')
    message('INFO', '        (see Examples below)')
    message('INFO', '')
    message('INFO', '')
    message('INFO', 'Examples:')
    message('INFO', '   - You wan\'t to check three directories\' Java files at once:')
    message('INFO', '')
    message('INFO', '     ' + sys.argv[0] + ' -p package_root1 -p package_root2 -p package_root3')
    message('INFO', '')
    message('INFO', '')
    message('INFO', '   - In the middle of typing, you wan\'t see the help; specifying')
    message('INFO', '     the option \'-h\' will print the help and exit:')
    message('INFO', '')
    message('INFO', '     ' + sys.argv[0] + ' -p package_root1 -p package_root2 -h')
    message('INFO', '')
    message('INFO', '')


def main():
    """ main:
          Parses imput options and starts up the calling sequence of functions 
          that will:

            1) Walk the directory tree
            2) Check for problems
            3) Warn about any Java files that have erroneus package statements
    """

    global binarynames

    packageroots = []

    # Parse parameters.
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hlp:')
    except getopt.GetoptError, (msg,opt):
        message('ERROR', msg)
        help()
        sys.exit(2)

    for o, a in opts:
        if o == '-h':
            help()
            sys.exit(0)
        elif o == '-l':
            binarynames = True
        elif o == '-p':
            packageroots.append(a)

    if not packageroots:
        message('ERROR', '')
        message('ERROR', 'You must specify at least one \'-p\' option')
        message('ERROR', '')
        sys.exit(1)

    # Check each package root's Java files in turn
    for x in packageroots:
        os.path.walk(x, walkcallback, None)


#
# Start it all up if called as a script.
if __name__ == '__main__':
    main()

